﻿using System;
using System.Globalization;
using System.Runtime.InteropServices;

namespace Microsoft.Goldenlight.Framework
{
    /// <summary>
    /// 	A way to assign groups to GameObjects. New Groups can be registered on the ObjectManager using the GetGroup(String name) method.
    /// </summary>
    [StructLayout(LayoutKind.Sequential)]
    public struct Group : IEquatable<Group>
    {
        #region Static Operations

        /// <summary>
        /// 	Object type which no objects will match.
        /// </summary>
        public static readonly Group None = new Group(0);

        /// <summary>
        /// 	Object type representing a union of all possible groups.
        /// </summary>
        public static readonly Group All = new Group(ulong.MaxValue);

        #region Equality & Inequality

        /// <summary>
        /// 	Returns a value that indicates whether two specified Group values are equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator ==(Group left, Group right)
        {
            return left._packedValue == right._packedValue;
        }

        /// <summary>
        /// 	Returns a value that indicates whether two specified Group values are not equal.
        /// </summary>
        /// <returns> true if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise, false. </returns>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        public static bool operator !=(Group left, Group right)
        {
            return left._packedValue != right._packedValue;
        }

        #endregion

        #region Implicit Convert

        /// <summary>
        /// 	Converts the value of the specified Group to a 64-bit unsigned integer.
        /// </summary>
        /// <param name="value"> The Group to convert. </param>
        /// <returns> The contents of value. </returns>
        public static implicit operator ulong(Group value)
        {
            return value._packedValue;
        }

        /// <summary>
        /// 	Converts a 64-bit unsigned integer to the specified Group.
        /// </summary>
        /// <param name="value"> The integer to convert. </param>
        /// <returns> The contents of value. </returns>
        public static implicit operator Group(ulong value)
        {
            return new Group(value);
        }

        #endregion

        #region Addition & Subtraction

        /// <summary>
        /// 	Take the union of two groups
        /// </summary>
        /// <param name="left"> First group in the union. </param>
        /// <param name="right"> Second group in the union. </param>
        /// <returns> </returns>
        public static Group operator +(Group left, Group right)
        {
            return new Group(left._packedValue | right._packedValue);
        }

        /// <summary>
        /// 	Take the difference between two groups.
        /// </summary>
        /// <param name="left"> Starting group. </param>
        /// <param name="right"> Remove this group(s). </param>
        /// <returns> </returns>
        public static Group operator -(Group left, Group right)
        {
            return new Group(left._packedValue & ~right._packedValue);
        }

        #endregion

        #region Bitwise And

        /// <summary>
        /// 	Test to see if the two group masks have any groups in common.
        /// </summary>
        /// <param name="left"> The first value to compare. </param>
        /// <param name="right"> The second value to compare. </param>
        /// <returns> </returns>
        public static Group operator &(Group left, Group right)
        {
            return new Group(left._packedValue & right._packedValue);
        }

        #endregion

        #endregion

        #region Fields & Properties

        private readonly ulong _packedValue;

        /// <summary>
        /// 	Present for Debug builds only, because we may change the internal representation of Group at a later time, which would break this interface.
        /// </summary>
        public ulong PackedValue
        {
            get
            {
                return _packedValue;
            }
        }

        /// <summary>
        /// 	Test whether a Group has been initialized.
        /// </summary>
        public bool IsValid
        {
            get
            {
                return _packedValue != 0;
            }
        }

        /// <summary>
        /// 	Test whether we are a single group or a union of several groups.
        /// </summary>
        public bool IsMultiple
        {
            get
            {
                var powerValue = Math.Log(_packedValue, 2);
                return (powerValue - (int) powerValue) > 1E-12;
            }
        }

        #endregion

        #region Constructors

        private Group(ulong packedValue)
        {
            _packedValue = packedValue;
        }

        #endregion

        #region Implementation of IEquatable<Group>

        /// <summary>
        /// 	Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns> true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false. </returns>
        /// <param name="other"> An object to compare with this object. </param>
        public bool Equals(Group other)
        {
            return _packedValue == other._packedValue;
        }

        #endregion

        #region Overrides of ValueType

        /// <summary>
        /// 	Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns> true if <paramref name="obj" /> and this instance are the same type and represent the same value; otherwise, false. </returns>
        /// <param name="obj"> Another object to compare to. </param>
        public override bool Equals(object obj)
        {
            var result = false;
            if (obj != null && obj is Group)
            {
                result = Equals((Group) obj);
            }
            return result;
        }

        /// <summary>
        /// 	Returns the hash code for this instance.
        /// </summary>
        /// <returns> A 64-bit unsigned integer that is the hash code for this instance. </returns>
        public override int GetHashCode()
        {
            return (int) _packedValue ^ (int) (_packedValue >> 0x20);
        }

        /// <summary>
        /// 	Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns> A <see cref="T:System.String" /> containing a fully qualified type name. </returns>
        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture, "0x{0:X16}", _packedValue);
        }

        #endregion
    }
}